package com.googlecode.veloeclipse.vaulttec.ui.model;

import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Preferences;

import com.googlecode.veloeclipse.editor.VelocityEditor;
import com.googlecode.veloeclipse.vaulttec.ui.IPreferencesConstants;
import com.googlecode.veloeclipse.vaulttec.ui.VelocityPlugin;
import com.googlecode.veloeclipse.vaulttec.ui.editor.text.VelocityTextGuesser;

/**
 * DOCUMENT ME!
 * 
 * @version $Revision: 7 $
 * @author <a href="mailto:akmal.sarhan@gmail.com">Akmal Sarhan </a>
 */
public class ModelTools
{

    private VelocityEditor fEditor;

    public ModelTools(VelocityEditor anEditor)
    {
        fEditor = anEditor;
    }

    /**
     * Uses visitor design pattern to find tree node which contains given line.
     * 
     * @param aLine
     *            line to find according tree node for
     * @return tree node containing given line or null if no tree node found
     */
    public ITreeNode getNodeByLine(int aLine)
    {
        ITreeNode node = fEditor.getRootNode();
        if (node != null)
        {
            TreeNodeLineVisitor visitor = new TreeNodeLineVisitor(aLine);
            node.accept(visitor);
            return visitor.getNode();
        }
        return null;
    }

    /**
     * Uses visitor design pattern to find tree node which contains given
     * guessed text.
     * 
     * @param aGuess
     *            document region marking a reference to find according tree
     *            node with referenced id for
     * @return tree node containing referenced id or null if no tree node found
     */
    public ITreeNode getNodeByGuess(VelocityTextGuesser aGuess)
    {
        ITreeNode node = fEditor.getRootNode();
        if (node != null)
        {
            String id;
            if (aGuess.getType() == VelocityTextGuesser.TYPE_VARIABLE)
            {
                id = "$" + aGuess.getText();
            } else if (aGuess.getType() == VelocityTextGuesser.TYPE_DIRECTIVE)
            {
                id = aGuess.getText();
            } else
            {
                return null;
            }
            // Search the model tree for a node with given ID
            TreeNodeIdVisitor visitor = new TreeNodeIdVisitor(id, aGuess.getLine());
            node.accept(visitor);
            return visitor.getNode();
        }
        return null;
    }

    /**
     * Returns true if specified line belongs to a <code>#foreach</code>
     * block.
     */
    public boolean isLineWithinLoop(int aLine)
    {
        ITreeNode node = fEditor.getRootNode();
        if (node != null)
        {
            if (aLine > 0)
            {
                // Use visitor pattern to find node which contains given line
                TreeNodeLineVisitor visitor = new TreeNodeLineVisitor(aLine);
                node.accept(visitor);
                node = visitor.getNode();
                while (node != null)
                {
                    if (node instanceof Directive)
                    {
                        Directive directive = (Directive) node;
                        if (directive.getType() == Directive.TYPE_FOREACH) { return true; }
                    }
                    node = (ITreeNode) node.getParent();
                }
            }
        }
        return false;
    }

    /**
     * DOCUMENT ME!
     * 
     * @param aLine
     *            DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     */
    public List getVariables(int aLine)
    {
        ITreeNode node = fEditor.getLastRootNode();
        if (node != null)
        {
            TreeNodeVariableVisitor visitor = new TreeNodeVariableVisitor(aLine);
            node.accept(visitor);
            List variables = visitor.getVariables();
            if (isLineWithinLoop(fEditor.getCursorLine()))
            {
                Preferences prefs = VelocityPlugin.getDefault().getPluginPreferences();
                String countName = "$" + prefs.getString(IPreferencesConstants.VELOCITY_COUNTER_NAME);
                variables.add(countName);
            }
            return variables;
        }
        return new ArrayList();
    }

    /**
     * DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     */
    public List getMacros()
    {
        ITreeNode node = fEditor.getLastRootNode();
        if (node != null)
        {
            TreeNodeMacroVisitor visitor = new TreeNodeMacroVisitor();
            node.accept(visitor);
            return visitor.getMacros();
        }
        return new ArrayList();
    }

    private class TreeNodeLineVisitor implements ITreeVisitor
    {

        private int       fLine;
        private ITreeNode fNode;

        public TreeNodeLineVisitor(int aLine)
        {
            fLine = aLine;
            fNode = null;
        }

        public boolean visit(ITreeNode aNode)
        {
            boolean more;
            if ((fLine >= aNode.getStartLine()) && (fLine <= aNode.getEndLine()))
            {
                fNode = aNode;
                more = false;
            } else
            {
                more = true;
            }
            return more;
        }

        public ITreeNode getNode()
        {
            return fNode;
        }
    }

    private class TreeNodeIdVisitor implements ITreeVisitor
    {

        private String    fId;
        private int       fLine;
        private ITreeNode fNode;

        public TreeNodeIdVisitor(String anId, int aLine)
        {
            fId = anId;
            fLine = aLine;
            fNode = null;
        }

        public boolean visit(ITreeNode aNode)
        {
            if ((aNode.getStartLine() < fLine) && aNode instanceof Directive)
            {
                Directive directive = ((Directive) aNode);
                // If within macro block then check macro parameters instead
                // of ID
                if (directive.getType() == Directive.TYPE_MACRO)
                {
                    if (directive.getId().equals(fId))
                    {
                        fNode = aNode;
                    } else if ((fLine >= aNode.getStartLine()) && (fLine <= aNode.getEndLine()))
                    {
                        List parameters = ((Directive) aNode).getParameters();
                        if ((parameters != null) && parameters.contains(fId))
                        {
                            fNode = aNode;
                        }
                    }
                } else
                {
                    String id = directive.getId();
                    if ((id != null) && id.equals(fId))
                    {
                        fNode = aNode;
                    }
                }
            }
            return true;
        }

        public ITreeNode getNode()
        {
            return fNode;
        }
    }

    private class TreeNodeVariableVisitor implements ITreeVisitor
    {

        private int  fLine;
        private List fVariables;

        public TreeNodeVariableVisitor(int aLine)
        {
            fLine = aLine;
            fVariables = new ArrayList();
        }

        public boolean visit(ITreeNode aNode)
        {
            if (aNode instanceof Directive)
            {
                int type = ((Directive) aNode).getType();
                if ((type == Directive.TYPE_FOREACH) || (type == Directive.TYPE_SET))
                {
                    String variable = ((Directive) aNode).getId();
                    if (!fVariables.contains(variable))
                    {
                        fVariables.add(variable);
                    }
                } else if (type == Directive.TYPE_MACRO)
                {
                    if ((fLine >= aNode.getStartLine()) && (fLine <= aNode.getEndLine()))
                    {
                        List parameters = ((Directive) aNode).getParameters();
                        if (parameters != null)
                        {
                            fVariables.addAll(parameters);
                        }
                    }
                }
            }
            return true;
        }

        public List getVariables()
        {
            return fVariables;
        }
    }

    private class TreeNodeMacroVisitor implements ITreeVisitor
    {

        private List fMacros;

        public TreeNodeMacroVisitor()
        {
            fMacros = new ArrayList();
        }

        public boolean visit(ITreeNode aNode)
        {
            if (aNode instanceof Directive)
            {
                int type = ((Directive) aNode).getType();
                if (type == Directive.TYPE_MACRO)
                {
                    fMacros.add(((Directive) aNode).getId());
                }
            }
            return true;
        }

        public List getMacros()
        {
            return fMacros;
        }
    }
}
